﻿/*
* Colorwheel
* Copyright (c) 2010 John Weir (http://famedriver.com)
* Licensed under the MIT (http://www.opensource.org/licenses/mit-license.php) license.
*
* requires jQuery & Raphael
*   http://jquery.com http://raphaeljs.com
*
* see http://jweir.github.com/colorwheel for Usage
*
*/

Raphael.colorwheel = function (target, color_wheel_size, no_segments) {
    var canvas,
      current_color,
      size,
      segments = no_segments || 60,
      bs_square = {},
      hue_ring = {},
      tri_size,
      cursor = {},
      drag_target,
      input_target,
      center,
      parent,
      change_callback,
      drag_callbacks = [function () { }, function () { } ],
      offset,
      padding = 2,
      sdim; // holds the dimensions for the saturation square

    function point(x, y) { return { x: x, y: y }; }
    function radians(a) { return a * (Math.PI / 180); }

    function angle(x, y) {
        var q = x > 0 ? 0 : 180;
        return q + Math.atan((0 - y) / (0 - x)) * 180 / (Math.PI);
    }

    function create(target, color_wheel_size) {
        size = color_wheel_size;
        tri_size = size / 20;
        center = size / 2;
        parent = $(target);
        canvas = Raphael(parent[0], size, size);
        canvas.safari();

        create_bs_square();
        create_hue_ring();
        hue_ring.cursor = cursor_create(tri_size);
        bs_square.cursor = cursor_create(tri_size * 0.5);
        events_setup();
        parent.css({ height: size + "px", width: size + "px" });
        disable_select(parent);
        return public_methods();
    }

    function disable_select(target) {
        $(target).css({ "unselectable": "on", "-moz-user-select": "none", "-webkit-user-select": "none" });
    }

    function public_methods() {
        return {
            input: input,
            onchange: onchange,
            ondrag: ondrag,
            color: public_set_color
        };
    }

    // Sets a textfield for user input of hex color values
    // TODO don't clear the change callback
    // TODO allow a null target to unbind the input
    function input(target) {
        change_callback = null;
        input_target = target;
        $(target).keyup(function () {
            if (this.value.match(/^#([0-9A-F]){3}$|^#([0-9A-F]){6}$/img)) {
                set_color(this.value);
                update_color(true);
                run_onchange_event();
            }
        });
        set_color(target.value);
        update_color(true);

        return public_methods();
    }

    function onchange(callback) {
        change_callback = callback;
        update_color(false);
        return public_methods();
    }

    function ondrag(start_callback, end_callback) {
        drag_callbacks = [start_callback || function () { }, end_callback || function () { } ];
        return public_methods();
    }

    function drag(e) {
        var x, y, page;

        e.preventDefault(); // prevents scrolling on touch

        page = e.originalEvent.touches ? e.originalEvent.touches[0] : e;

        x = page.pageX - (parent.offset().left + center);
        y = page.pageY - (parent.offset().top + center);

        if (drag_target == hue_ring) {
            set_hue_cursor(x, y);
            update_color();
            run_onchange_event();
            return true;
        }
        if (drag_target == bs_square) {
            set_bs_cursor(x, y);
            update_color();
            run_onchange_event();
            return true;
        }
    }

    function start_drag(event, target) {
        event.preventDefault(); // prevents scrolling on touch

        $(document).on('mouseup touchend', stop_drag);
        $(document).on('mousemove touchmove', drag);
        drag_target = target;
        drag(event);
        drag_callbacks[0](current_color);
    }

    function stop_drag(event) {
        event.preventDefault(); // prevents scrolling on touch

        $(document).off("mouseup touchend", stop_drag);
        $(document).off("mousemove touchmove", drag);
        drag_callbacks[1](current_color);
        run_onchange_event();
    }

    function events_setup() {
        $([hue_ring.event.node, hue_ring.cursor[0].node]).on("mousedown touchstart",
                                                        function (e) { start_drag(e, hue_ring); });
        $([bs_square.b.node, bs_square.cursor[0].node]).on("mousedown touchstart",
                                                       function (e) { start_drag(e, bs_square); });
    }

    function cursor_create(size) {
        var set = canvas.set().push(
        canvas.circle(0, 0, size).attr({ "stroke-width": 4, stroke: "#333" }),
        canvas.circle(0, 0, size + 2).attr({ "stroke-width": 1, stroke: "#FFF", opacity: 0.5 })
    );

        set[0].node.style.cursor = "crosshair";

        return set;
    }

    function set_bs_cursor(x, y) {
        x = x + center;
        y = y + center;
        if (x < sdim.x) { x = sdim.x }
        if (x > sdim.x + sdim.l) { x = sdim.x + sdim.l }
        if (y < sdim.y) { y = sdim.y }
        if (y > sdim.y + sdim.l) { y = sdim.y + sdim.l }

        bs_square.cursor.attr({ cx: x, cy: y }).transform("t0,0");
    }


    function set_hue(color) {
        var hex = Raphael.getRGB(color).hex;
        bs_square.h.attr("fill", hex);
    }

    function hue() {
        return Raphael.rgb2hsb(bs_square.h.attr("fill")).h;
    }

    function public_set_color(value) {
        var ret = set_color(value);
        update_color(false);
        return ret;
    }

    function set_color(value) {
        if (value === undefined) { return current_color; }

        var temp = canvas.rect(1, 1, 1, 1).attr({ fill: value }),
        hsb = canvas.raphael.rgb2hsb(temp.attr("fill"));

        set_bs_cursor(
      (0 - sdim.l / 2) + (sdim.l * hsb.s),
      sdim.l / 2 - (sdim.l * hsb.b));
        set_hue_cursor((360 * (hsb.h)) - 90);
        temp.remove();
        return public_methods();
    }

    // Could optimize this method
    function update_color(dont_replace_input_value) {
        var x = bs_square.cursor.items[0].attr("cx"),
        y = bs_square.cursor.items[0].attr("cy"),
        hsb = {
            b: 1 - (y - sdim.y) / sdim.l,
            s: (x - sdim.x) / sdim.l,
            h: hue()
        };

        current_color = Raphael.hsb2rgb(hsb.h, hsb.s, hsb.b);

        if (input_target) {
            var c = current_color.hex;
            if (dont_replace_input_value !== true) { input_target.value = c; }
            if (hsb.b < 0.5) {
                $(input_target).css("color", "#FFF");
            } else {
                $(input_target).css("color", "#000");
            }
            input_target.style.background = c;
        }

    }

    // accepts either x,y or d (degrees)
    function set_hue_cursor(mixed_args) {
        var d;
        if (arguments.length == 2) {
            d = angle(arguments[0], arguments[1]);
        } else {
            d = arguments[0];
        }

        var x = Math.cos(radians(d)) * (center - tri_size - padding);
        var y = Math.sin(radians(d)) * (center - tri_size - padding);
        hue_ring.cursor.attr({ cx: x + center, cy: y + center }).transform("t0,0");
        set_hue("hsb(" + (d + 90) / 360 + ",1,1)");
    }

    function bs_square_dim() {
        if (sdim) { return sdim; }
        var s = size - (tri_size * 4);
        sdim = {
            x: (s / 6) + tri_size * 2 + padding,
            y: (s / 6) + tri_size * 2 + padding,
            l: (s * 2 / 3) - padding * 2
        };
        return sdim;
    }

    function create_bs_square() {
        bs_square_dim();
        box = [sdim.x, sdim.y, sdim.l, sdim.l];

        bs_square.h = canvas.rect.apply(canvas, box).attr({
            stroke: "#EEE", gradient: "0-#FFF-#000", opacity: 1
        });
        bs_square.s = canvas.rect.apply(canvas, box).attr({
            stroke: null, gradient: "0-#FFF-#FFF", opacity: 0
        });
        bs_square.b = canvas.rect.apply(canvas, box).attr({
            stroke: null, gradient: "90-#000-#FFF", opacity: 0
        });
        bs_square.b.node.style.cursor = "crosshair";
    }

    function hue_segement_shape() {
        var path = "M -@W 0 L @W 0 L @W @H L -@W @H z";
        return path.replace(/@H/img, tri_size * 2).replace(/@W/img, tri_size);
    }

    function copy_segment(r, d, k) {
        var n = r.clone();
        var hue = d * (255 / k);

        var s = size / 2,
      t = tri_size,
      p = padding;

        n.transform("t" + s + "," + (s - t) + "r" + (360 / k) * d + "t0,-" + (s - t - p) + "");

        n.attr({ "stroke-width": 0, fill: "hsb(" + d * (1 / k) + ", 1, 0.85)" });
        hue_ring.hues.push(n);
    }

    function create_hue_ring() {
        var s = hue_segement_shape(),
        tri = canvas.path(s).attr({ stroke: "rgba(0,0,0,0)" }).transform("t" + (size / 2) + "," + padding),
        k = segments; // # of segments to use to generate the hues

        hue_ring.hues = canvas.set();

        for (n = 0; n < k; n++) { copy_segment(tri, n, k); }

        // IE needs a slight opacity to assign events
        hue_ring.event = canvas.circle(
      center,
      center,
      center - tri_size - padding).attr({ "stroke-width": tri_size * 2, opacity: 0.01 });

        hue_ring.outline = canvas.circle(
      center,
      center,
      center - tri_size - padding).attr({ "stroke": "#000", "stroke-width": (tri_size * 2) + 3, opacity: 0.1 });
        hue_ring.outline.toBack();
        hue_ring.event.node.style.cursor = "crosshair";
    }

    function run_onchange_event() {
        if (({}).toString.call(change_callback).match(/function/i)) {
            change_callback(current_color);
        }
    }

    return create(target, color_wheel_size);
};